//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: ObjectHolderUtility.cs
//
//  Description: Utility to simplify the work with ObjectHolderBase classes.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Chili.Opf3.Query.OPathParser;
using Chili.Opf3.Query;
using System.Reflection;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Relations
{
	/// <summary>
	/// Internal utility to simplify the work with the <see cref="ObjectHolderBase">ObjectHolderBase</see> derived classes.
	/// </summary>
	internal static class ObjectHolderUtility
	{
		#region DeleteManyToMany

		/// <summary>
		/// Deletes the objects in the weak entity. Those objects are the connection between the child objects and
		/// the parent object. Only the connection is deleted since in a many-to-many relation it's not known if the
		/// child objects aren't used by anybody else. Attention: This method deletes the connection between two objects
		/// in the many-to-many connection if the user marked one of the child objects as deleted. The child object is
		/// NOT deleted. That's very important, because in a many-to-many connection the child object may be in 
		/// connection with another parent object!
		/// </summary>
		/// <param name="attribute">The <see cref="RelationAttribute">RelationAttribute</see> specified on the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="context">The <see cref="ObjectContext">ObjectContext</see> of the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="list">The managed object in the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="parent">The parent object of the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		public static void DeleteManyToMany<T>(IList<T> list, RelationAttribute attribute, object parent, ObjectContext context)
		{
			TypeMapping parentMapping = TypeMapping.GetTypeMapping(parent);

			ObjectSet<T> objectSet = list as ObjectSet<T>;
			if (objectSet != null)
			{
				// Loop over all items in the RemovedList and delete all objects in the weak entity.
				foreach (object obj in objectSet.RemovedList)
				{
					DeleteWeakObject(attribute, parent, context, parentMapping, obj);
				}
				// Clear the removed list, since the objects in the weak entity are deleted now. We can't
				// delete the associated objects, since they could be connected with other objects.
				objectSet.RemovedList.Clear();
			}
			else
			{
				// Loop over all items in the list.
				for (int i = list.Count - 1; i >= 0; i--)
				{
					// Check if the item is to delete.
					object obj = list[i];
					if (context.GetObjectInfo(obj).MarkForDeletion)
					{
						// Delete the weak object and remove the item from the list.
						DeleteWeakObject(attribute, parent, context, parentMapping, obj);
						list.RemoveAt(i);
					}
				}
			}
		}

		/// <summary>
		/// Deletes the persistent object in the weak entity.
		/// </summary>
		/// <param name="attribute">The <see cref="RelationAttribute">RelationAttribute</see> specified on the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="context">The <see cref="ObjectContext">ObjectContext</see> of the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="parent">The parent object of the <see cref="ObjectHolderBase">ObjectHolderBase</see>.</param>
		/// <param name="parentMapping">The mapping of the parent persistent object type.</param>
		/// <param name="obj">The object that is deleted.</param>
		private static void DeleteWeakObject(RelationAttribute attribute, object parent, ObjectContext context, TypeMapping parentMapping, object obj)
		{
			// Get the RelationInfo object that represents the relation.
			IRelationInfo info = parentMapping.Relations.GetRelationMapping(attribute.Name);

			// Create the ObjectExpression to load the object.
			TypeMapping weakMapping = info.WeakTypeMapping;
			ObjectExpression objectExpression = new ObjectExpression(QueryTypes.Delete, weakMapping.PersistentType, weakMapping.PersistentAttribute);
			objectExpression.Items.Add(new OEWhereCondition());
			IList<OEItem> item = objectExpression.Items[0].Items;

			// Loop over all relations from the parent persistent to the weak persistent.
			foreach (RelatedPropertiesPair pair in info.RelatedProperties)
			{
				// Get the value from the parent object.
				object value = pair.ParentMember.GetValue(parent);

				OECondition condition = new OECondition();
				condition.Items.Add(new OEMember(pair.ChildMember.Name, pair.ChildMember.FieldAttribute, info.WeakTypeMapping.PersistentAttribute));
				condition.Items.Add(new OEParameter(value, pair.ParentMember.MemberType));
				item.Add(condition);
			}

			// Loop over all relations from the weak persistent to the child object.
			foreach (RelatedPropertiesPair pair in info.WeakRelatedProperties)
			{
				// Get the value from the parent object.
				object value = pair.ChildMember.GetValue(obj);

				OECondition condition = new OECondition();
				condition.Items.Add(new OEMember(pair.ParentMember.Name, pair.ParentMember.FieldAttribute, info.WeakTypeMapping.PersistentAttribute));
				condition.Items.Add(new OEParameter(value, pair.ChildMember.MemberType));
				item.Add(condition);
			}

			// Execute the ObjectExpression on the storage.
			context.Storage.ExecuteCommand(objectExpression);
		}

		#endregion

		#region SaveManyToMany

		/// <summary>
		/// Creates the connection between the two objects in the many-to-many relation. The connection is 
		/// only created if not done earlier. Therefore the routine has to do a select query and then an insert,
		/// if the select does not return anything.
		/// </summary>
		public static void SaveManyToMany<T>(IList<T> list, RelationAttribute attribute, object parent, ObjectContext context)
		{
			TypeMapping parentMapping = TypeMapping.GetTypeMapping(parent);
			MethodInfo methodInfo = context.GetType().GetMethod("GetObject", new Type[] { typeof(IQuery) }).MakeGenericMethod(attribute.WeakPersistent);
			// Get the RelationInfo object that represents the relation.
			IRelationInfo info = parentMapping.Relations.GetRelationMapping(attribute.Name);

			foreach (object obj in list)
			{
				TypeMapping weakMapping = info.WeakTypeMapping;
				PersistentAttribute weakAttribute = weakMapping.PersistentAttribute;

				// Create the ObjectExpression to load the object.
				ObjectExpression objectExpression = new ObjectExpression(QueryTypes.Load, weakMapping.PersistentType, weakMapping.PersistentAttribute);
				objectExpression.Items.Add(new OEWhereCondition());
				IList<OEItem> item = objectExpression.Items[0].Items;

				// Loop over all relations from the parent persistent to the weak persistent.
				foreach (RelatedPropertiesPair pair in info.RelatedProperties)
				{
					// Get the value from the parent object.
					object value = pair.ParentMember.GetValue(parent);

					OECondition condition = new OECondition();
					condition.Items.Add(new OEMember(pair.ChildMember.Name, pair.ChildMember.FieldAttribute, weakAttribute));
					condition.Items.Add(new OEConcat("="));
					condition.Items.Add(new OEParameter(value, pair.ParentMember.MemberType));

					// Add an And concat if there are more then one conditions.
					if (item.Count > 0)
						item.Add(new OEConcat("And"));
					item.Add(condition);
				}

				// Loop over all relations from the weak persistent to the child object.
				foreach (RelatedPropertiesPair pair in info.WeakRelatedProperties)
				{
					// Get the value from the parent object.
					object value = pair.ChildMember.GetValue(obj);

					OECondition condition = new OECondition();
					condition.Items.Add(new OEMember(pair.ParentMember.Name, pair.ParentMember.FieldAttribute, weakAttribute));
					condition.Items.Add(new OEConcat("="));
					condition.Items.Add(new OEParameter(value, pair.ChildMember.MemberType));

					item.Add(new OEConcat("And"));
					item.Add(condition);
				}

				// Use the method and the ObjectExpression to get the weak object.
				object weakObj = methodInfo.Invoke(context, new object[] { objectExpression });

				// If no object has been returned we have to insert one in the weak entity.
				if (weakObj != null)
					continue;

				// Create the ObjectExpression to insert the object in the weak entity.
				ObjectExpression insertObjectExpression = new ObjectExpression(QueryTypes.Insert, weakMapping.PersistentType, weakMapping.PersistentAttribute);

				// Loop over all relations from the parent persistent to the weak persistent.
				foreach (RelatedPropertiesPair pair in info.RelatedProperties)
				{
					// Get the value from the parent object.
					object value = pair.ParentMember.GetValue(parent);

					// Create a condition and add it to the ObjectExpressions base item.
					OECondition condition = new OECondition();
					condition.Items.Add(new OEMember(pair.ChildMember.Name, pair.ChildMember.FieldAttribute, weakAttribute));
					condition.Items.Add(new OEParameter(value, pair.ParentMember.MemberType));
					insertObjectExpression.Items.Add(condition);
				}

				// Loop over all relations from the weak persistent to the child object.
				foreach (RelatedPropertiesPair pair in info.WeakRelatedProperties)
				{
					// Get the value from the parent object.
					object value = pair.ChildMember.GetValue(obj);

					OECondition condition = new OECondition();
					condition.Items.Add(new OEMember(pair.ParentMember.Name, pair.ParentMember.FieldAttribute, weakAttribute));
					condition.Items.Add(new OEParameter(value, pair.ChildMember.MemberType));
					insertObjectExpression.Items.Add(condition);
				}

				// Execute the ObjectExpression on the storage. The storage is used here to avoid the ConcurrencyManager.
				context.Storage.ExecuteCommand(insertObjectExpression);
			}
		}

		#endregion

		#region PrepareQuery

		/// <summary>
		/// Prepares the <see cref="Opf3.Query.IQuery">IQuery</see> for the ObjectHolder.
		/// </summary>
		/// <param name="attribute">The relation attribute for which the query is generated.</param>
		/// <param name="query">The query that has been pre set.</param>
		/// <param name="parent">The parent object (The persistent object that contains the class inheriting 
		/// from <see cref="ObjectHolderBase">ObjectHolderBase</see>).</param>
		/// <returns>Returns a class that implements the <see cref="Opf3.Query.IQuery">IQuery</see> interface.
		/// It is used to get the objects from the storage.</returns>
		public static IQuery PrepareQuery<T>(RelationAttribute attribute, IQuery query, object parent)
		{
			if (attribute == null || parent == null)
				return null;

			// Return if the query is != null and not an Object query.
			if (query != null && !(query is ObjectQuery<T>))
				return query;

			ObjectExpression objectExpression = null;
			IList<OEItem> items = null;
			// Check if the query is not null.
			if (query != null)
			{
				objectExpression = OPath.Parse<T>((ObjectQuery<T>)query);

				if (objectExpression.Items[0] is OESortBy)
				{
					items = objectExpression.Items[1].Items;
				}
				else
				{
					items = objectExpression.Items[0].Items;
					// Add the created concat. Attention: First has to be inserted the concat then the condtion.
					items.Insert(0, new OEConcat("And"));
					items.Insert(0, new OEBrackets());
					items = items[0].Items;
				}
			}
			else
			{
				TypeMapping mapping = TypeMapping.GetTypeMapping(typeof(T));
				// Create a new ObjectExpression.
				objectExpression = new ObjectExpression(QueryTypes.Load, mapping.PersistentType, mapping.PersistentAttribute);
				objectExpression.Items.Add(new OEWhereCondition());
				items = objectExpression.Items[0].Items;
			}

			// Get the ObjectMapping for the parent object and the child object.
			TypeMapping parentMapping = TypeMapping.GetTypeMapping(parent);
			TypeMapping childMapping1 = null;
			// If required get the ObjectMapping for the weak persistent object.
			if (attribute.WeakPersistent == null)
				childMapping1 = TypeMapping.GetTypeMapping(typeof(T));
			else
				childMapping1 = TypeMapping.GetTypeMapping(attribute.WeakPersistent);

			// Get the RelationInfo object that represents the relation.
			IRelationInfo info = parentMapping.Relations.GetRelationMapping(attribute.Name);

			// Loop over all relations in the attribute.
			foreach (RelatedPropertiesPair pair in info.RelatedProperties)
			{
				// Get the value from the parent object.
				object value = pair.ParentMember.GetValue(parent);
				FieldAttribute childFieldAttribute = pair.ChildMember.FieldAttribute;

				// If the value is null return.
				if (value == null)
					return null;

				// Create a condition that is added to the created ObjectExpression.
				OECondition condition = new OECondition();
				condition.Items.Add(new OEMember(pair.ChildMember.Name, childFieldAttribute, childMapping1.PersistentAttribute));
				condition.Items.Add(new OEComparer("="));
				condition.Items.Add(new OEParameter(value, pair.ChildMember.MemberType));

				// Add an and concat if we have already a condition in the list.
				if (items.Count > 0)
					items.Add(new OEConcat("And"));
				items.Add(condition);
			}

			// If there is a weak persistent, we need also to connect the weak persistent.
			if (attribute.WeakPersistent != null)
			{
				TypeMapping childMapping2 = TypeMapping.GetTypeMapping(typeof(T));

				foreach (RelatedPropertiesPair pair in info.WeakRelatedProperties)
				{
					FieldAttribute parentFieldAttribue = pair.ParentMember.FieldAttribute;
					FieldAttribute childFieldAttribute = pair.ChildMember.FieldAttribute;

					// Create a condition that is added to the created ObjectExpression.
					OECondition condition = new OECondition();
					condition.Items.Add(new OEMember(pair.ParentMember.Name, parentFieldAttribue, childMapping1.PersistentAttribute));
					condition.Items.Add(new OEComparer("="));
					condition.Items.Add(new OEMember(pair.ChildMember.Name, childFieldAttribute, childMapping2.PersistentAttribute));

					items.Add(new OEConcat("And"));
					items.Add(condition);
				}
				// Add the persistent attribute of the weak object.
				objectExpression.PersistentAttributes.Add(childMapping1.PersistentAttribute);
			}
			return objectExpression;
		}

		#endregion
	}
}
